package com.javacoo.junit.generator.internal;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.javacoo.junit.generator.api.ImplementFinder;
import com.javacoo.junit.generator.internal.data.PrimitiveType;
import com.javacoo.junit.generator.internal.data.Tuple;
import com.javacoo.junit.generator.internal.data.WrapperType;
import com.javacoo.junit.generator.model.ConstructHierarchy;
import com.javacoo.xkernel.spi.ExtensionLoader;

import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import static com.javacoo.junit.generator.internal.util.Formats.BLANK_4;

/**
 * 类型值
 * <li></li>
 * @author duanyong@jccfc.com
 * @date 2021/1/7 14:54
 */
public class TypeValueCode {
    /**
     * 依赖的对象集合
     */
    private final List<String> dependencyClasses;
    /**
     * 类型名称
     */
    private final String typeName;
    /**
     * 方法体字符集合
     */
    public List<String> clauses = new ArrayList<>();
    /**
     * 支持的类型集合
     */
    private static List<String> supportTypes = new ArrayList<>();
    /**
     * 结构层次
     */
    private ConstructHierarchy constructRoot;
    /**
     * 是否是构造器最后一个参数
     */
    boolean endConstruct = false;
    /**
     * 结构层次
     */
    private VariableNames variableNames;

    static {
        supportTypes.add("Collection");
        supportTypes.add("Map");
        supportTypes.add("List");
        supportTypes.add("Set");
    }

    public TypeValueCode(String typeName, List<String> dependencyClasses, VariableNames variableNames) {
        this.typeName = typeName;
        this.dependencyClasses = dependencyClasses;
        this.variableNames = variableNames;

        Tuple tuple = splitType(typeName);
        String outerType = tuple.first();
        constructRoot = new ConstructHierarchy(getClass(outerType));
    }

    private boolean supportOrNot(String type) {
        return supportTypes.contains(type);
    }

    public String infer(String expectVarName) {
        return get(typeName, constructRoot, expectVarName);
    }
    /**
     * 获取当前变量名
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:07
     * @param type: 类型
     * @param constructHierarchy: 层级
     * @param expectVarName: 预期变量名
     * @return: java.lang.String currentVarName
     */
    public String get(String type, ConstructHierarchy constructHierarchy, String expectVarName) {
        String formerVarName_1 = null;
        String formerVarName_2 = null;

        //计算对象元组
        Tuple tuple = splitType(type);
        String outerType = tuple.first();
        String innerType = tuple.second();

        int dimension = tuple.third();
        //获取外层类对象
        Class c = getClass(outerType);
        if (c == null) {
            return null;
        }
        //如果是构造方法最后一个参数
        if (endConstruct) {
            //获取方法申明的参数类型元组对象
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            //如果是包装类型
            if(WrapperType.contains(c)){
                clauses.add(tp.second() + WrapperType.getValue(c) + ";");
            }else{
                clauses.add(tp.second() + "null;");
            }
            endConstruct = false;
            return tp.first();
        }

        //计算innerType
        if (innerType != null) {
            String[] types = new String[2];
            types[0] = innerType;
            if (!innerType.contains(",")) {
                //types[0] = innerType;
                types[1] = null;
            } else {
                int ltCnt = 0;
                for (int i = 0; i < innerType.length(); i++) {
                    char cha = innerType.charAt(i);
                    if (cha == ',' && ltCnt == 0) {
                        types[0] = innerType.substring(0, i).trim();
                        types[1] = innerType.substring(i + 1).trim();
                        break;
                    }
                    if (cha == '<') {
                        ltCnt++;
                    } else if (cha == '>') {
                        ltCnt--;
                    }
                }
            }

            if (types[1] == null) {
                formerVarName_1 = get(types[0], constructHierarchy, null);
            } else {
                formerVarName_1 = get(types[0], constructHierarchy, null);
                formerVarName_2 = get(types[1], constructHierarchy, null);
            }
        }
        String currentVarName;

        //计算outerType
        outerType = outerType.contains(".") ? outerType.substring(outerType.lastIndexOf(".") + 1) : outerType;
        //isArray
        if (dimension > 0) {
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            clauses.add((tp.second() + "new " + outerType + "[1]" + Strings.repeat("[]", dimension - 1) + ";"));
            clauses.add((currentVarName = tp.first()) + "[0] = " + formerVarName_1 + ";");
        } else if (Collection.class.isAssignableFrom(c)) {
            addJavaUtilPackage();
            if (outerType.endsWith("List") || outerType.endsWith("Collection")) {
                if (outerType.equals("List") || outerType.equals("AbstractList") || outerType.equals("Collection")) {
                    Tuple tp = getDeclaration(c, type, dimension, expectVarName);
                    currentVarName = tp.first();
                    clauses.add(tp.second() + "new ArrayList<>();");
                } else {
                    currentVarName = other(c, type, dimension, constructHierarchy, expectVarName);
                }

            } else if (outerType.endsWith("Set")) {
                if (outerType.equals("Set") || outerType.equals("AbstractSet")) {
                    Tuple tp = getDeclaration(c, type, dimension, expectVarName);
                    currentVarName = tp.first();
                    clauses.add(tp.second() + "new HashSet<>();");
                } else {
                    currentVarName = other(c, type, dimension, constructHierarchy, expectVarName);
                }

            } else if (outerType.endsWith("Queue")) {
                if (outerType.equals("Queue") || outerType.equals("AbstractQueue")) {
                    Tuple tp = getDeclaration(c, type, dimension, expectVarName);
                    currentVarName = tp.first();
                    clauses.add(tp.second() + "new LinkedList<>();");
                } else {
                    currentVarName = other(c, type, dimension, constructHierarchy, expectVarName);
                }
            } else {
                currentVarName = other(c, type, dimension, constructHierarchy, expectVarName);
            }
            clauses.add(currentVarName + ".add(" + formerVarName_1 + ");");

        } else if (Map.class.isAssignableFrom(c) || Dictionary.class.isAssignableFrom(c)) {
            addJavaUtilPackage();
            if (outerType.endsWith("Map")) {
                if (outerType.equals("Map") || outerType.equals("AbstractMap") || outerType.equals("HashMap")) {
                    Tuple tp = getDeclaration(c, type, dimension, expectVarName);
                    currentVarName = tp.first();
                    clauses.add(tp.second() + "new HashMap<>();");
                } else {
                    currentVarName = other(c, type, dimension, constructHierarchy, expectVarName);
                }
            } else if (outerType.equals("Dictionary") || outerType.endsWith("Hashtable")) {
                Tuple tp = getDeclaration(c, type, dimension, expectVarName);
                currentVarName = tp.first();
                clauses.add(tp.second() + "new Hashtable<>();");
            } else {
                currentVarName = other(c, type, dimension, constructHierarchy, expectVarName);
            }

            if (formerVarName_1 != null || formerVarName_2 != null || c.getName().startsWith("java.util")) {
                clauses.add(currentVarName + ".put(" + formerVarName_1 + ", " + formerVarName_2 + ");");
            }

        } else if (c.isPrimitive()) {
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            currentVarName = tp.first();
            clauses.add(tp.second() + PrimitiveType.get(c) + ";");
        } else if (WrapperType.contains(c)) {
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            currentVarName = tp.first();
            clauses.add(tp.second() + WrapperType.getValue(c) + ";");
        } else if (c.isEnum()) {
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            currentVarName = tp.first();
            Field[] fields = c.getFields();
            String enumTypeName = tp.second().toString().split(" ")[0];
            if ("Enum".equals(enumTypeName) || fields.length == 0) {
                clauses.add(tp.second() + "null;");
            } else {
                clauses.add(tp.second() + enumTypeName + "." + fields[0].getName() + ";");
            }
        } else {
            currentVarName = other(c, type, dimension, constructHierarchy, expectVarName);
        }

        return currentVarName;
    }
    /**
     * 添加集合依赖
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:38
     * @return: void
     */
    private void addJavaUtilPackage() {
        if (!dependencyClasses.contains("java.util.*")) {
            dependencyClasses.add("java.util.*");
        }
    }
    /**
     * 获取方法申明的参数类型元组对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:11
     * @param c:类对象
     * @param type:类型
     * @param dimension:维度
     * @param expectVarName:预期的变量名
     * @return: com.javacoo.junit.generator.internal.data.Tuple
     */
    private Tuple getDeclaration(Class c, String type, int dimension, String expectVarName) {
        String currentVarName;
        if (expectVarName == null) {
            if (dimension > 0) {
                currentVarName = variableNames.get(Array.newInstance(c, 0).getClass());
            } else {
                currentVarName = variableNames.get(c);
            }
        } else {
            currentVarName = variableNames.get(expectVarName);
        }

        //better simplify all class eg. java.util.List<java.lang.String> --> List<String>
        int i = type.indexOf("<");
        String type1 = type;

        boolean hasDollarCharacter = false;
        if (i != -1) {
            type1 = type.substring(0, i);

            int $index = type.indexOf("$");
            if ($index > i) {
                hasDollarCharacter = true;
            }
        }

        Class c1 = c;
        while (c1.isArray()) {
            c1 = c1.getComponentType();
        }

        //获取包装类型
        Class outClass = c1.getEnclosingClass();
        if (outClass != null) {
            String outClassName = outClass.getTypeName();
            String s = c.getTypeName().substring(0, outClassName.length()) + "." + c.getTypeName().substring(outClassName.length() + 1);
            return Tuple.of(currentVarName, s + " " + currentVarName + " = ", false);
        }

        boolean useSimpleName = true;
        if (!dependencyClasses.contains(c1.getName())) {
            useSimpleName = false;
        }

        String declareType;
        if (!useSimpleName) {
            declareType = type;
        } else {
            declareType = type.substring(type1.lastIndexOf('.') + 1);
        }

        if (hasDollarCharacter) {
            declareType = declareType.substring(0, declareType.indexOf("<"));
        }
        return Tuple.of(currentVarName, declareType + " " + currentVarName + " = ", useSimpleName);
    }

    /**
     * 处理其他类型
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:24
     * @param c:类对象
     * @param type:类型
     * @param dimension:维度
     * @param constructHierarchy:层级
     * @param expectVarName:预期的变量名
     * @return: java.lang.String
     */
    private String other(Class c, String type, int dimension, ConstructHierarchy constructHierarchy, String expectVarName) {
        String varName;
        //如果是接口或者抽象类型
        if (c.isInterface() || Modifier.isAbstract(c.getModifiers())) {
            //查找实现类或者子类
            Optional<List<Class>> optionalImplClasses = scanImpl(c);
            if (optionalImplClasses.isPresent() && !optionalImplClasses.get().isEmpty()) {
                //默认取第一个
                c = optionalImplClasses.get().get(0);
                addDependency(c);
            } else {
                type = type.contains("<") ? type.substring(0, type.indexOf("<")) : type;
                Tuple tp = getDeclaration(c, type, dimension, expectVarName);
                varName = tp.first();
                clauses.add(tp.second() + "null;");
                return varName;
            }
        }
        //使用构造器
        varName = useConstructor(c, type, dimension, constructHierarchy, expectVarName);
        return varName;

    }
    /**
     * 使用构造器
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:26
     * @param c:类对象
     * @param type:类型
     * @param dimension:维度
     * @param constructHierarchy:层级
     * @param expectVarName:预期的变量名
     * @return: java.lang.String
     */
    private String useConstructor(Class c, String type, int dimension, ConstructHierarchy constructHierarchy, String expectVarName) {
        String varName;
        if ("String".equals(c.getSimpleName())) {
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            varName = tp.first();
            clauses.add(tp.second() + "\"hello\";");
            return varName;
        }

        //inner class
        if (c.getEnclosingClass() != null && !Modifier.isStatic(c.getModifiers())) {
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            varName = tp.first();
            clauses.add(tp.second() + "null;");
            return varName;
        }
        //如果有带参数的构造函数
        if (c.getConstructors().length > 0) {
            Constructor ctor = MethodHelper.chooseConstructor(c.getConstructors(), true);
            if (ctor != null) {
                String[] expectedVarNames = MethodHelper.getExpectedVarNames(ctor, c);

                Type[] ctorParams = ctor.getGenericParameterTypes();

                Class[] dependendyClasses = getDependendyClass(ctorParams);
                addDependency(dependendyClasses);

                String[] ctorArgs = new String[ctorParams.length];
                for (int i = 0; i < ctorParams.length; i++) {
                    ConstructHierarchy child = new ConstructHierarchy(ctorParams[i].getClass());
                    child.setParent(constructHierarchy);
                    constructHierarchy.addChildren(child);
                    if (child.repeatWithParent()) {
                        endConstruct = true;
                    }
                    ctorArgs[i] = get(ctorParams[i].getTypeName(), child, expectedVarNames[i]);
                }
                Tuple tp = getDeclaration(c, type, dimension, expectVarName);
                varName = tp.first();
                String s = tp.third() ? c.getSimpleName() : getName(c);

                String ex = MethodHelper.needTryCatch(ctor);
                if (ex != null) {
                    clauses.add(tp.second() + "null;");
                    clauses.add("try {");
                    clauses.add(BLANK_4 + varName + " = new " + s + "(" + Joiner.on(", ").join(ctorArgs) + ");");
                    String exceptionVarName = variableNames.get("ex", false);
                    clauses.add("} catch (" + ex + " " + exceptionVarName + ") {");
                    clauses.add(BLANK_4 + exceptionVarName + ".printStackTrace();");
                    clauses.add(BLANK_4 + "fail(\"Unexpected exception: \"" + " + " + exceptionVarName + ");");
                    clauses.add("}");
                } else {
                    clauses.add(tp.second() + "new " + s + "(" + Joiner.on(", ").join(ctorArgs) + ");");
                }
                return varName;
            }

        }

        Method factoryMethod;
        if ((factoryMethod = getFactoryMethod(c)) != null) {
            String[] expectedVarNames = MethodHelper.getExpectedVarNames(factoryMethod, c);
            Class[] params = factoryMethod.getParameterTypes();
            addDependency(params);

            String[] args = new String[params.length];
            for (int i = 0; i < params.length; i++) {
                ConstructHierarchy child = new ConstructHierarchy(params[i]);
                child.setParent(constructHierarchy);
                constructHierarchy.addChildren(child);
                if (child.repeatWithParent()) {
                    endConstruct = true;
                }
                args[i] = get(params[i].getTypeName(), child, expectedVarNames[i]);
            }
            Tuple tp = getDeclaration(c, type, dimension, expectVarName);
            varName = tp.first();
            String typeName = tp.third() ? c.getSimpleName() : getName(c);

            String ex = MethodHelper.needTryCatch(factoryMethod);
            if (ex != null) {
                clauses.add(tp.second() + "null;");
                clauses.add("try {");
                clauses.add(BLANK_4 + varName + " = " + typeName + "." + factoryMethod.getName() + "(" + Joiner.on(", ").join(args) + ");");
                String exceptionVarName = variableNames.get("ex", false);
                clauses.add("} catch (" + ex + " " + exceptionVarName + ") {");
                clauses.add(BLANK_4 + exceptionVarName + ".printStackTrace();");
                clauses.add(BLANK_4 + "fail(\"Unexpected exception: \"" + " + " + exceptionVarName + ");");
                clauses.add("}");
            } else {
                clauses.add(tp.second() + typeName + "." + factoryMethod.getName() + "(" + Joiner.on(", ").join(args) + ");");
            }

            return varName;
        }
        Tuple tp = getDeclaration(c, type, dimension, expectVarName);
        varName = tp.first();
        clauses.add(tp.second() + "null;");
        return varName;

    }
    /**
     * 获取依赖对象数组
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 10:47
     * @param Types:
     * @return: java.lang.Class[]
     */
    private Class[] getDependendyClass(Type[] Types) {
        List<Class> dependencyClassList = new ArrayList<>(5);
        for(Type type: Types){
            if (type instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                for (Type argType : actualTypeArguments) {
                    dependencyClassList.add(argType.getClass());
                }
            }else{
                dependencyClassList.add(type.getClass());
            }
        }
        return dependencyClassList.toArray(new Class[dependencyClassList.size()]);
    }
    /**
     * 获取类名称
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:37
     * @param cls:类对象
     * @return: java.lang.String
     */
    private String getName(Class<?> cls) {
        String s = cls.getTypeName();
        Class outClass = cls.getEnclosingClass();
        if (outClass != null) {
            String outClassName = outClass.getTypeName();
            s = cls.getTypeName().substring(0, outClassName.length()) + "." + cls.getTypeName().substring(outClassName.length() + 1);
        }
        return s;
    }
    /**
     * 获取工厂方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:36
     * @param c:类对象
     * @return: java.lang.reflect.Method
     */
    private Method getFactoryMethod(Class c) {
        for (Method method : c.getDeclaredMethods()) {
            if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
                if (method.getReturnType().equals(c)) {
                    if (method.getGenericReturnType() instanceof ParameterizedTypeImpl) {
                        return null;
                    }
                    return method;
                }
            }
        }
        return null;
    }
    /**
     * 获取类的元组对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:04
     * @param type: 类型
     * @return: com.javacoo.junit.generator.internal.data.Tuple
     */
    private Tuple splitType(String type) {
        String outer, inner;
        //维度
        int dimension = 0;

        if (type.endsWith("[]")) {
            String typeWithOutSpace = type.replace(" ", "");
            for (int i = typeWithOutSpace.length() - 1; i >= 0; i -= 2) {
                if (typeWithOutSpace.charAt(i) == ']') {
                    dimension++;
                } else {
                    break;
                }
            }

            if (type.contains("<")) {
                outer = type.substring(0, type.indexOf('<'));
            } else {
                outer = type.substring(0, type.indexOf('['));
            }
            inner = type.substring(0, type.lastIndexOf('['));

        } else if (type.contains("<")) {
            outer = type.substring(0, type.indexOf('<'));
            inner = type.substring(type.indexOf('<') + 1, type.lastIndexOf('>'));
        } else {
            outer = type;
            inner = null;

        }
        return Tuple.of(outer, inner, dimension);
    }
    /**
     * 添加依赖
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:35
     * @param classes: 类对象集合
     * @return: void
     */
    private void addDependency(Class... classes) {
        for (int i = 0; i < classes.length; i++) {
            if (!classes[i].getName().contains(".")) {
                continue;
            }

            Class c = classes[i];
            while (c.isArray()) {
                c = c.getComponentType();
            }

            //nested class
            if (c.getDeclaringClass() != null) {
                return;
            }

            String name = c.getName();
            String shortName = name.substring(name.lastIndexOf(".") + 1);

            boolean b = false;
            for (int j = 0; j < dependencyClasses.size(); j++) {
                String importClass = dependencyClasses.get(j);
                if (importClass.substring(importClass.lastIndexOf(".") + 1).equals(shortName)) {
                    b = true;
                    break;
                }
            }
            if (b) {
                continue;
            }
            dependencyClasses.add(name);
        }
    }
    /**
     * 根据类型获取类对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 15:35
     * @param type:类型
     * @return: java.lang.Class 类对象
     */
    private Class getClass(String type) {
        Class result = null;
        switch (type) {
            case "int":
                result = int.class;
                break;
            case "long":
                result = long.class;
                break;
            case "short":
                result = short.class;
                break;
            case "char":
                result = char.class;
                break;
            case "float":
                result = float.class;
                break;
            case "double":
                result = double.class;
                break;
            case "boolean":
                result = boolean.class;
                break;
            case "byte":
                result = byte.class;
                break;
            case "String":
                result = String.class;
                break;
        }

        if(result == null){
            try {
                result = ObjectFactory.internalClassForName(type);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        if (result != null) {
            addDependency(result);
        }
        return result;
    }
    /**
     * 获取接口实现类
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/7 11:48
     * @param interfaceClass:
     * @return: java.util.Optional<java.util.List<java.lang.Class>>
     */
    private Optional<List<Class>> scanImpl(Class interfaceClass) {
        ImplementFinder implementFinder = ExtensionLoader.getExtensionLoader(ImplementFinder.class).getDefaultExtension();
        return implementFinder.find(interfaceClass);
    }

}
